package web_report

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"math/rand"
	"net/http"
	"peilian-api/app/global/databases/tables"
	"peilian-api/app/global/log"
	"peilian-api/app/global/variable"
	"peilian-api/app/model"
	"peilian-api/utils/tools"
	"reflect"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
)

var (
	FetchLanguageComment      func(string) float64
	FetchMood                 func(string) float64
	FetchEmotion              func(string) float64
	FetchFocusTopicScore      func(string, string) float64
	FetchEvaluationScore      func([]byte) *model.LabelEvaluate
	FetchSkillComment         func(string, float64) string
	CalculateStandardVariance func([]float64) (float64, float64)
	FetchSkillCommentExt      func(string, float64, float64, float64, float64, bool) string
	FetchLogicScore           func(string, string) float64
	FetchPromoteComment       func(string, int, string) string
	FetchCharacter            func(string) (model.CharacterResp, error)
	SuggestComments           map[string]map[string]map[string]string
	AbilityComments           map[string]map[string]map[string]string
	FetchLabelsCache          func(*gin.Context, string, string) string
)

type scoreCache struct {
	Scores   []float64
	Variance float64
	Avg      float64
}

type ReportData struct {
	Ctx      *gin.Context
	Session  *tables.Session
	RealQues []*tables.RealQues
	Data     *AllData
	Logger   *log.MyLogger

	IWebReport

	evaluateResp          *model.LabelEvaluate
	avgLanguageScore      float64 // 语言表达能力
	avgThemeScore         float64 // 主题把控力
	avgExpressiveScore    float64
	fluencyChartCache     scoreCache
	asrCache              scoreCache
	moodCache             scoreCache
	toneCache             scoreCache
	logicScoreCache       scoreCache // 逻辑性
	performanceScoreCache scoreCache
	personalityScore      map[string]float64
	allAnswerContent      string
}

func (rd *ReportData) UpdateEveryQuestionScore() error {
	if rd.RealQues == nil {
		rd.RealQues = make([]*tables.RealQues, 0)
		if err := variable.DB.MySQL.Preload("Question").Order("id asc").
			Find(&rd.RealQues, "session_id = ?", rd.Session.ID).Error; err != nil {
			return err
		}
	}
	for _, q := range rd.RealQues {
		// 大五人格
		if err := rd.SetCharacterData(q); err != nil {
			return err
		}
		// 更新题目解析字段，缓存获取到的标签
		if len(q.PartLabelsCache) == 0 && rd.Session.Career.ReportType == "web" {
			q.PartLabelsCache = FetchLabelsCache(rd.Ctx, rd.Session.Career.Name, q.AnswerText)
			variable.DB.MySQL.Model(q).Select("PartLabelsCache").Updates(q)
		}

		if len(q.AnswerText) < 50 || q.SpeakTime < 10 {
			continue
		}
		for i := 0; i < 3; i++ {
			if len(q.AnswerAudio) == 0 {
				rq, _ := new(tables.RealQues).GetRealQuestionById(int(q.ID))
				q = &rq
				time.Sleep(300 * time.Millisecond)
				continue
			}
			break
		}
		// 语调气场
		var answerAudioUrl string
		if q.AnswerAudio == "hbzp.pcm" {
			answerAudioUrl = q.Audio
		} else {
			answerAudioUrl = variable.Config.UfsInfo.UfsUrl + variable.Config.UfsInfo.BucketPrefix + q.AnswerAudio
		}
		q.Tone = FetchEmotion(answerAudioUrl)
		// asr 评分
		q.AsrScore = FetchLanguageComment(q.AnswerText)
		// 自信度(语气词评分)
		q.Mood = FetchMood(q.AnswerText)
		// 语言流畅度
		q.LanguageFluencyScore = q.AsrScore*0.5 + q.Mood*0.5
		// 语言表达能力 = (语言流畅度+语调) / 2
		q.LanguageScore = (q.Tone + q.LanguageFluencyScore) / 2.0
		// 围绕主题得分
		q.ThemeScore = FetchFocusTopicScore(q.QuestionText, q.AnswerText)
		// 面试状态-表达能力
		//
		q.TopicScore = q.LanguageScore*0.2 + q.ThemeScore*0.3
		// 逻辑评分
		q.LogicScore = FetchLogicScore(q.QuestionText, q.AnswerText)
		if q.ThemeScore < q.LogicScore {
			// 逻辑得分是与围绕主题得分相比取最小值
			q.LogicScore = q.ThemeScore
		}
		//q.ExpressScore = (q.LanguageFluencyScore + q.Tone + q.ThemeScore + q.LogicScore) / 4.0
		q.ExpressScore = (q.LanguageFluencyScore + q.Tone) / 4.0
		// 表现力
		q.PerformanceScore = q.LanguageScore*0.5 + q.ThemeScore*0.5
		var characterInfoByte []byte
		if q.Cr != nil {
			characterInfoByte, _ = json.Marshal(q.Cr.Personality)
		} else {
			characterInfoByte = []byte("")
		}
		q.CharacterInfo = string(characterInfoByte)
		updateFields := []string{
			"Tone", "AsrScore", "LanguageFluencyScore", "LanguageScore", "ThemeScore",
			"ExpressScore", "TopicScore", "LogicScore", "PerformanceScore", "CharacterInfo",
		}
		if err := variable.DB.MySQL.Model(q).Select(updateFields).Updates(q).Error; err != nil {
			return err
		}
	}
	return nil
}

func (rd *ReportData) GetPosProfessionalData() []LabelValue {
	chartsData := make([]LabelValue, 0)
	if rd.evaluateResp.PosWordScore.Profession != nil {
		for _, pos := range rd.evaluateResp.PosWordScore.Profession {
			score := 60 + int(pos[1].(float64)*100*25/100)
			score += rand.Intn(10) + 1
			chartsData = append(chartsData, LabelValue{
				Label: pos[0].(string),
				Value: score,
			})
		}
	}
	return chartsData
}

func (rd *ReportData) GetNegProfessionalData() []LabelValue {
	chartsData := make([]LabelValue, 0)
	if rd.evaluateResp.NegScoreDict.Profession != nil {
		for _, neg := range rd.evaluateResp.NegScoreDict.Profession {
			score := int(neg[1].(float64) * 100 * 60 / 100)
			chartsData = append(chartsData, LabelValue{
				Label: neg[0].(string),
				Value: score,
			})
		}
	}
	return chartsData
}

func (rd *ReportData) GetProfessionalData() *ScoreChart {
	chartsData := make([]LabelValue, 0)
	chartsData = append(chartsData, rd.GetPosProfessionalData()...)
	chartsData = append(chartsData, rd.GetNegProfessionalData()...)
	return &ScoreChart{
		Score:     int(rd.evaluateResp.LabelScore.Profession * 100),
		ChartData: chartsData,
	}
}

func (rd *ReportData) GetInstrumentalData() []string {
	instrumentalData := make([]string, 0)
	if rd.evaluateResp.NegScoreDict.Tool != nil {
		for _, neg := range rd.evaluateResp.NegScoreDict.Tool {
			instrumentalData = append(instrumentalData, neg[0].(string))
		}
	}
	if rd.evaluateResp.ExpScoreDict.Tool != nil {
		for _, exp := range rd.evaluateResp.ExpScoreDict.Tool {
			if len(instrumentalData) >= 5 {
				break
			}
			instrumentalData = append(instrumentalData, exp[0].(string))
		}
	}
	return instrumentalData
}

func (rd *ReportData) GetExperienceData() *Experience {
	advantageData := make([]LabelValue, 0)
	promotedData := make([]LabelValue, 0)
	if rd.evaluateResp.PosWordScore.Experience != nil {
		for _, pos := range rd.evaluateResp.PosWordScore.Experience {
			advantageData = append(advantageData, LabelValue{
				Label: pos[0].(string),
				Value: int(pos[1].(float64) * 100),
			})
		}
	}
	if rd.evaluateResp.NegScoreDict.Experience != nil {
		for _, neg := range rd.evaluateResp.NegScoreDict.Experience {
			promotedData = append(promotedData, LabelValue{
				Label: neg[0].(string),
				Value: int(neg[1].(float64) * 100),
			})
		}
	}
	if rd.evaluateResp.ExpScoreDict.Experience != nil {
		for _, exp := range rd.evaluateResp.ExpScoreDict.Experience {
			if len(promotedData) >= 5 {
				break
			}
			promotedData = append(promotedData, LabelValue{
				Label: exp[0].(string),
				Value: int(exp[1].(float64) * 100),
			})
		}
	}
	return &Experience{
		Advantage: advantageData,
		Promoted:  promotedData,
	}
}

func (rd *ReportData) GetPersonalData() []LabelValue {
	personalData := make([]LabelValue, 0)
	if rd.evaluateResp.PosWordScore.Individuality != nil {
		for _, pos := range rd.evaluateResp.PosWordScore.Individuality {
			personalData = append(personalData, LabelValue{
				Label: pos[0].(string),
				Value: int(pos[1].(float64) * 100),
			})
		}
	}
	if rd.evaluateResp.NegScoreDict.Individuality != nil {
		for _, neg := range rd.evaluateResp.NegScoreDict.Individuality {
			personalData = append(personalData, LabelValue{
				Label: neg[0].(string),
				Value: 0,
			})
		}
	}
	if rd.evaluateResp.ExpScoreDict.Individuality != nil {
		for _, v := range rd.evaluateResp.ExpScoreDict.Individuality {
			if len(personalData) >= 15 {
				break
			}
			personalData = append(personalData, LabelValue{
				Label: v[0].(string),
				Value: 0,
			})
		}
	}
	return personalData
}

func (rd *ReportData) GetOccupationMapData(professional, GeneralData *ScoreChart) *OccupationMap {
	professionalAbilityData := make([]LabelValue, 0)
	universalAbilityData := make([]LabelValue, 0)
	professionalAbilityData = append(professionalAbilityData, professional.ChartData...)
	universalAbilityData = append(universalAbilityData, GeneralData.ChartData...)
	for _, v := range rd.evaluateResp.ExpScoreDict.Profession {
		if len(professionalAbilityData) >= 15 {
			break
		}
		professionalAbilityData = append(professionalAbilityData, LabelValue{
			Label: v[0].(string),
			Value: 0,
		})
	}
	for _, v := range rd.evaluateResp.ExpScoreDict.General {
		if len(universalAbilityData) >= 15 {
			break
		}
		universalAbilityData = append(universalAbilityData, LabelValue{
			Label: v[0].(string),
			Value: 0,
		})
	}
	return &OccupationMap{
		ProfessionalAbility: professionalAbilityData,
		UniversalAbility:    universalAbilityData,
	}
}

func (rd *ReportData) GetExpressiveData() *Expressive {
	fluencyData := &ChartInfo{
		ChartData: rd.fluencyChartCache.Scores,
		Evaluation: FetchSkillCommentExt(
			"status_0",
			rd.fluencyChartCache.Avg,
			rd.fluencyChartCache.Variance,
			rd.asrCache.Avg,
			rd.moodCache.Avg,
			false,
		),
	}
	intonationData := &ChartInfo{
		ChartData: rd.toneCache.Scores,
		Evaluation: FetchSkillCommentExt(
			"status_1",
			rd.toneCache.Avg,
			rd.toneCache.Variance,
			0.0,
			0.0,
			true,
		),
	}
	emphasisData := &ScoreInfo{
		Score:      int(rd.avgThemeScore),
		Evaluation: FetchSkillComment("status_2", rd.avgThemeScore),
	}
	logicData := &ScoreInfo{
		Score:      int(rd.logicScoreCache.Avg),
		Evaluation: FetchPromoteComment("4", int(rd.logicScoreCache.Avg), "逻辑思维能力"),
	}
	return &Expressive{
		Score:      int(rd.avgExpressiveScore), // 表达能力分数
		Evaluation: FetchSkillComment("status_4", rd.avgExpressiveScore),
		Fluency:    fluencyData,
		Intonation: intonationData,
		Emphasis:   emphasisData, // 重点把控力得分
		Logic:      logicData,    // 逻辑得分
	}
}

func (rd *ReportData) GetInterviewState() (*InterviewState, error) {
	expressive := rd.GetExpressiveData()
	performance := &ChartInfo{
		ChartData: rd.performanceScoreCache.Scores,
		Evaluation: FetchSkillCommentExt(
			"status_5",
			rd.performanceScoreCache.Avg,
			rd.performanceScoreCache.Variance,
			0.0,
			0.0,
			true,
		),
	}
	confidence := &ChartInfo{
		ChartData: rd.moodCache.Scores,
		Evaluation: FetchSkillCommentExt(
			"status_6",
			rd.moodCache.Avg,
			rd.moodCache.Variance,
			0.0,
			0.0,
			true,
		),
	}
	return &InterviewState{
		ExpressiveAbility: expressive,
		Performance:       performance,
		Confidence:        confidence,
	}, nil
}

func (rd *ReportData) Generate() error {
	rd.Logger.Info("正在生成报告中。。。。")
	//skillComments, _ = tables.DefaultTrick.GetSkillTrick()
	AbilityComments, _ = tables.DefaultTrick.GetAbilityTrick()
	//suggestCommonComments, _ = tables.DefaultTrick.GetCommonSuggestTrick()
	SuggestComments, _ = tables.DefaultTrick.GetSuggestTrick()

	userImgUrl := variable.Config.UfsInfo.UfsUrl + variable.Config.UfsInfo.BucketPrefix + rd.Session.UserPhoto
	duration := rd.Session.FinishAt.Sub(rd.Session.CreatedAt).Seconds()
	durationStr := fmt.Sprintf("%d分%d秒", int(duration/60), int(duration)%60)

	if err := rd.UpdateEveryQuestionScore(); err != nil {
		return err
	}
	interviewScore, err := rd.GetInterviewScore()
	if err != nil {
		return err
	}
	vocationalData, err := rd.GetVocationalData()
	if err != nil {
		return err
	}
	interviewState, err := rd.GetInterviewState()
	if err != nil {
		return err
	}
	guide, err := rd.GetGuideData()
	if err != nil {
		return err
	}
	personality, err := rd.GetPersonalityData()
	if err != nil {
		return err
	}
	// 兼容河北招聘网
	var userName = rd.Session.User.Name
	if rd.Session.User.ID == 1 {
		userName = "-"
		// 查询用户名
		interview, _ := new(tables.Interview).GetInterview(int(rd.Session.ID))
		if interview.ID != 0 {
			userName = interview.UserName
		}
	}
	careerId := rd.Session.CareerID
	careerName := rd.Session.Career.Name
	userId := rd.Session.UserID

	// 河北的特殊处理
	if rd.Session.CareerID == 45 {
		careerId = 0
		userId = 0
		careerName = rd.Session.CareerName
	}

	scoreOverView, err := rd.GetScoreOverView()
	if err != nil {
		return err
	}

	rd.Data = &AllData{
		UserImgUrl:        userImgUrl,
		Time:              rd.Session.FinishAt.Format("2006-01-02 15:04:05"),
		Duration:          durationStr,
		UserName:          userName,
		InterviewScore:    interviewScore,
		VocationalAbility: vocationalData,
		Major:             careerName,
		InterviewState:    interviewState,
		Guide:             guide,
		Personality:       personality,
		Type:              rd.Session.Career.ReportType,
		CareerId:          careerId,
		UserId:            userId,
		ScoreOverView:     scoreOverView,
	}
	go func() {
		defer tools.RecoverGoroutine()
		if err := rd.RecordScore(); err != nil {
			rd.Logger.Error(err.Error())
		}
		if err := rd.SaveRecommendCareerLabels(); err != nil {
			rd.Logger.Warn(err.Error())
		}
	}()
	return nil
}

func (rd *ReportData) SaveRecommendCareerLabels() error {
	allPosLabels := make([]string, 0)
	v := reflect.ValueOf(rd.evaluateResp.PosWordScore)
	t := reflect.TypeOf(rd.evaluateResp.PosWordScore)
	for i := 0; i < v.NumField(); i++ {
		labels := v.Field(i).Interface().([][]interface{})
		for _, l := range labels {
			if len(l) != 2 {
				continue
			}
			allPosLabels = append(allPosLabels, l[0].(string)+"-"+t.Field(i).Name)
		}
	}
	if len(allPosLabels) <= 0 {
		return errors.New("没有优势标签，不进行岗位推荐")
	}
	recommendReq := map[string]interface{}{"label_word_list": allPosLabels}
	resp, err := rd.HttpClient(recommendReq, variable.Config.Services.CareerRecommend, "POST")
	if err != nil {
		return err
	}
	var respStruct CareerRecommendResp
	if err := json.Unmarshal(resp, &respStruct); err != nil {
		return err
	}
	if len(respStruct.Data) <= 0 {
		return errors.New("岗位推荐算法返回结果中没有推荐岗位")
	}
	careerLi := make([]string, 0)
	for _, c := range respStruct.Data {
		if len(careerLi) >= 2 {
			break
		}
		careerLi = append(careerLi, c[0].(string))
	}
	if err := variable.DB.MySQL.Model(&tables.User{}).Where("id=?", rd.Session.UserID).
		Update("RecommendCareer", strings.Join(careerLi, ",")).Error; err != nil {
		return err
	}
	return nil
}

func (rd *ReportData) HttpClient(req interface{}, url, method string) ([]byte, error) {
	reqJsonData, _ := json.Marshal(req)
	rd.Logger.Debug(fmt.Sprintf("请求[%s]参数: ", url), string(reqJsonData))
	request, err := http.NewRequest(
		method,
		url,
		bytes.NewReader(reqJsonData),
	)
	if err != nil {
		return nil, err
	}
	request.Header.Set("Content-Type", "application/json")
	client := &http.Client{
		Timeout: 50 * time.Second,
	}
	resp, err := client.Do(request)
	if err != nil {
		rd.Logger.ErrorF(rd.Ctx, "请求失败: "+err.Error())
		return nil, err
	}
	if resp != nil {
		defer resp.Body.Close()
	}

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	rd.Logger.Info(string(body))
	return body, nil
}

func (rd *ReportData) RecordScore() error {
	evalScore := tables.ScoreFeedBack{
		SessionID: rd.Session.ID,
		ScoreType: "eval_score",
		Score:     int(rd.evaluateResp.EvalScore * 100),
	}
	total := tables.ScoreFeedBack{
		SessionID: rd.Session.ID,
		ScoreType: "total",
		Score:     rd.Data.InterviewScore,
	}
	expressive := tables.ScoreFeedBack{
		SessionID: rd.Session.ID,
		ScoreType: "expressive",
		Score:     rd.Data.InterviewState.ExpressiveAbility.Score,
	}
	theme := tables.ScoreFeedBack{
		SessionID: rd.Session.ID,
		ScoreType: "theme",
		Score:     rd.Data.InterviewState.ExpressiveAbility.Emphasis.Score,
	}
	logic := tables.ScoreFeedBack{
		SessionID: rd.Session.ID,
		ScoreType: "logic",
		Score:     rd.Data.InterviewState.ExpressiveAbility.Logic.Score,
	}
	sessionScores := []tables.ScoreFeedBack{evalScore, total, expressive, theme, logic}
	switch rd.Data.VocationalAbility.(type) {
	case *ProfessionalData:
		vocationalData := rd.Data.VocationalAbility.(*ProfessionalData)
		profession := tables.ScoreFeedBack{
			SessionID: rd.Session.ID,
			ScoreType: "profession",
			Score:     vocationalData.Professional.Score,
		}
		general := tables.ScoreFeedBack{
			SessionID: rd.Session.ID,
			ScoreType: "general",
			Score:     vocationalData.GeneralPurpose.Score,
		}
		sessionScores = append(sessionScores, profession, general)
	}
	variable.DB.MySQL.Unscoped().Where("session_id=?", rd.Session.ID).Delete(&tables.ScoreFeedBack{})
	if err := variable.DB.MySQL.Create(&sessionScores).Error; err != nil {
		return err
	}
	return nil
}
